package repository

import (
	"errors"
	"fmt"
	"os"
	"runtime"
	"sort"
	"strings"
	"time"

	"gopkg.openfuyao.cn/bkecommon"
	"gopkg.openfuyao.cn/bkecommon/cluster/validation"

	econd "gopkg.openfuyao.cn/bkeadm/pkg/executor/containerd"
	"gopkg.openfuyao.cn/bkeadm/pkg/global"
	"gopkg.openfuyao.cn/bkeadm/pkg/infrastructure"
	"gopkg.openfuyao.cn/bkeadm/pkg/server"
	"gopkg.openfuyao.cn/bkeadm/utils"
	"gopkg.openfuyao.cn/bkeadm/utils/log"
)

var (
	imageFile          = fmt.Sprintf("%s/%s-%s", global.Workspace, utils.ImageFile, runtime.GOARCH)
	imageDataFile      = fmt.Sprintf("%s/%s", global.Workspace, utils.ImageDataFile)
	imageDataDirectory = fmt.Sprintf("%s/%s", global.Workspace, utils.ImageDataDirectory)
	yumDataFile        = fmt.Sprintf("%s/%s", global.Workspace, utils.SourceDataFile)
	yumDataDirectory   = fmt.Sprintf("%s/%s", global.Workspace, utils.SourceDataDirectory)
	chartDataFile      = fmt.Sprintf("%s/%s", global.Workspace, utils.ChartDataFile)
	chartDataDirectory = fmt.Sprintf("%s/%s", global.Workspace, utils.ChartDataDirectory)
	nfsDataFile        = fmt.Sprintf("%s/%s", global.Workspace, utils.NFSDataFile)
	nfsDataDirectory   = fmt.Sprintf("%s/%s", global.Workspace, utils.NFSDataDirectory)
)

func PrepareRepositoryDependOn() (err error) {
	if !utils.Exists(imageDataDirectory) || utils.DirectoryIsEmpty(imageDataDirectory) {
		_ = os.Remove(imageDataDirectory)
		if utils.Exists(imageDataFile) {
			log.BKEFormat(log.INFO, "Decompressing the image package...")
			// Ensure integrity of the decompressed file
			targetTemp := imageDataDirectory + ".tmp"
			if utils.Exists(targetTemp) {
				err = os.RemoveAll(targetTemp)
				if err != nil {
					return err
				}
			}
			err = utils.UnTar(imageDataFile, targetTemp)
			if err != nil {
				return err
			}
			err = os.Rename(targetTemp, imageDataDirectory)
			if err != nil {
				return err
			}
		} else {
			err = os.Mkdir(imageDataDirectory, 0644)
			if err != nil {
				return err
			}
		}
		if !utils.Exists(imageDataDirectory) {
			return errors.New(fmt.Sprintf("%s, not found", imageDataDirectory))
		}
	} else {
		log.BKEFormat(log.WARN, "If the image file already exists, skip decompressing the volumes/image.tar.gz file. ")
		log.BKEFormat(log.HINT, fmt.Sprintf("If you want to unzip it again, remove the directory %s", imageDataDirectory))
	}

	if !utils.Exists(chartDataDirectory) || utils.DirectoryIsEmpty(chartDataDirectory) {
		_ = os.Remove(chartDataDirectory)
		if utils.Exists(chartDataFile) {
			log.BKEFormat(log.INFO, "Decompressing the chart source package...")
			// Ensure integrity of the decompressed file
			err = utils.UnTar(chartDataFile, global.Workspace+"/mount")
			if err != nil {
				return err
			}
		} else {
			err = os.Mkdir(chartDataDirectory, 0644)
			if err != nil {
				return err
			}
		}
		if !utils.Exists(chartDataDirectory) {
			return errors.New(fmt.Sprintf("%s, not found", chartDataDirectory))
		}
	} else {
		log.BKEFormat(log.WARN, "If the charts file already exists, skip decompressing the mount/charts.tar.gz file. ")
		log.BKEFormat(log.HINT, fmt.Sprintf("If you want to unzip it again, remove the directory %s", chartDataDirectory))
	}

	if !utils.Exists(nfsDataDirectory) || utils.DirectoryIsEmpty(nfsDataDirectory) {
		_ = os.Remove(nfsDataDirectory)
		if utils.Exists(nfsDataFile) {
			log.BKEFormat(log.INFO, "Decompressing the nfsshare source package...")
			// Ensure integrity of the decompressed file
			targetTemp := global.Workspace + "/mount/nfsshare.tmp"
			if utils.Exists(targetTemp) {
				err = os.RemoveAll(targetTemp)
				if err != nil {
					return err
				}
			}
			err = os.MkdirAll(targetTemp, 0644)
			if err != nil {
				return err
			}
			err = utils.UnTar(nfsDataFile, targetTemp)
			if err != nil {
				return err
			}
			if utils.Exists(targetTemp + "/nfsshare") {
				if err = os.Rename(targetTemp+"/nfsshare", nfsDataDirectory); err != nil {
					return err
				}
				_ = os.Remove(targetTemp)
			} else {
				err = os.Rename(targetTemp, nfsDataDirectory)
				if err != nil {
					return err
				}
			}
		} else {
			err = os.Mkdir(nfsDataDirectory, 0644)
			if err != nil {
				return err
			}
		}
		if !utils.Exists(nfsDataDirectory) {
			return errors.New(fmt.Sprintf("%s, not found", nfsDataDirectory))
		}
	} else {
		log.BKEFormat(log.WARN, "If the nfsshare file already exists, skip decompressing the mount/nfsshare.tar.gz file. ")
		log.BKEFormat(log.HINT, fmt.Sprintf("If you want to unzip it again, remove the directory %s", nfsDataDirectory))
	}
	return nil
}

func LoadLocalRepository() (err error) {
	if utils.Exists(imageFile) {
		if infrastructure.IsDocker() {
			_, err = global.Docker.Load(imageFile)
			if err != nil {
				return err
			}
		}
		if infrastructure.IsContainerd() {
			err = econd.Load(imageFile)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func ContainerServer(imageRegistryPort, otherRepo string) error {
	image := fmt.Sprintf("%s%s", otherRepo, utils.DefaultLocalImageRegistry)
	if otherRepo != "" {
		image = fmt.Sprintf("%s%s", otherRepo, utils.DefaultLocalImageRegistry)
	}

	err := server.StartImageRegistry(utils.LocalImageRegistryName, image, imageRegistryPort, imageDataDirectory)
	if err != nil {
		log.BKEFormat(log.WARN, fmt.Sprintf("Start image registry failed, %s", err.Error()))
		_ = server.RemoveImageRegistry(utils.LocalImageRegistryName)
		for i := 0; i < 3; i++ {
			log.BKEFormat(log.INFO, "Retrying to start image registry...")
			time.Sleep(2 * time.Second)
			err = server.StartImageRegistry(utils.LocalImageRegistryName, image, imageRegistryPort, imageDataDirectory)
			if err == nil {
				return nil
			} else {
				_ = server.RemoveImageRegistry(utils.LocalImageRegistryName)
			}
		}
	}
	return err
}

func YumServer(imageRegistryPort, yumRegistryPort, otherRepo string) error {
	image := fmt.Sprintf("0.0.0.0:%s/%s/%s", imageRegistryPort, bkecommon.ImageRegistryKubernetes, utils.DefaultLocalYumRegistry)
	if otherRepo != "" {
		image = fmt.Sprintf("%s%s", otherRepo, utils.DefaultLocalYumRegistry)
	}

	err := server.StartYumRegistry(utils.LocalYumRegistryName, image, yumRegistryPort, yumDataDirectory)
	if err != nil {
		log.BKEFormat(log.WARN, fmt.Sprintf("Start yum registry failed, %s", err.Error()))
		_ = server.RemoveYumRegistry(utils.LocalYumRegistryName)
		for i := 0; i < 3; i++ {
			log.BKEFormat(log.INFO, "Retrying to start yum registry...")
			time.Sleep(2 * time.Second)
			err = server.StartYumRegistry(utils.LocalYumRegistryName, image, yumRegistryPort, yumDataDirectory)
			if err == nil {
				return nil
			} else {
				_ = server.RemoveYumRegistry(utils.LocalYumRegistryName)
			}
		}
	}
	return err
}

func ChartServer(imageRegistryPort, chartRegistryPort, otherRepo string) error {
	image := fmt.Sprintf("0.0.0.0:%s/%s/%s", imageRegistryPort, bkecommon.ImageRegistryKubernetes, utils.DefaultLocalChartRegistry)
	if otherRepo != "" {
		image = fmt.Sprintf("%s%s", otherRepo, utils.DefaultLocalChartRegistry)
	}

	err := server.StartChartRegistry(utils.LocalChartRegistryName, image, chartRegistryPort, chartDataDirectory)
	if err != nil {
		log.BKEFormat(log.WARN, fmt.Sprintf("Start chart registry failed, %s", err.Error()))
		_ = server.RemoveChartRegistry(utils.LocalChartRegistryName)
		for i := 0; i < 3; i++ {
			log.BKEFormat(log.INFO, "Retrying to start chart registry...")
			time.Sleep(2 * time.Second)
			err = server.StartChartRegistry(utils.LocalChartRegistryName, image, chartRegistryPort, chartDataDirectory)
			if err == nil {
				return nil
			} else {
				_ = server.RemoveChartRegistry(utils.LocalChartRegistryName)
			}
		}
	}
	return err
}

func NFSServer(imageRegistryPort, otherRepo string) error {
	image := fmt.Sprintf("0.0.0.0:%s/%s/%s", imageRegistryPort, bkecommon.ImageRegistryKubernetes, utils.DefaultLocalNFSRegistry)
	if otherRepo != "" {
		image = fmt.Sprintf("%s%s", otherRepo, utils.DefaultLocalNFSRegistry)
	}

	err := server.StartNFSServer(utils.LocalNFSRegistryName, image, nfsDataDirectory)
	if err != nil {
		log.BKEFormat(log.WARN, fmt.Sprintf("Start nfs server failed, %s", err.Error()))
		_ = server.RemoveNFSServer(utils.LocalNFSRegistryName)
		for i := 0; i < 3; i++ {
			log.BKEFormat(log.INFO, "Retrying to start nfs server...")
			time.Sleep(2 * time.Second)
			err = server.StartNFSServer(utils.LocalNFSRegistryName, image, nfsDataDirectory)
			if err == nil {
				return nil
			} else {
				_ = server.RemoveNFSServer(utils.LocalNFSRegistryName)
			}
		}
	}
	return err
}

func VerifyContainerdFile() (string, []string, []string, error) {
	filePath := fmt.Sprintf("%s/%s", yumDataDirectory, "files")
	containerdList := []string{}
	cniPluginList := []string{}
	entries, err := os.ReadDir(filePath)
	if err != nil {
		return filePath, containerdList, cniPluginList, err
	}
	for _, entry := range entries {
		if strings.HasPrefix(entry.Name(), utils.CniPluginPrefix) {
			cniPluginList = append(cniPluginList, entry.Name())
			continue
		}
		if err = validation.ValidateCustomExtra(map[string]string{"containerd": entry.Name()}); err != nil {
			continue
		}
		containerdList = append(containerdList, entry.Name())
	}
	if len(containerdList) == 0 {
		return filePath, containerdList, cniPluginList, errors.New("a valid containerd cannot be found")
	}
	if len(cniPluginList) == 0 {
		return filePath, containerdList, cniPluginList, errors.New("a valid cni plugin cannot be found")
	}
	if len(containerdList) == 1 {
		global.CustomExtra["containerd"] = containerdList[0]
	}
	cds := strings.Split(containerdList[len(containerdList)-1], "-")
	cds[len(cds)-1] = "{.arch}.tar.gz"
	if len(containerdList) > 1 {
		global.CustomExtra["containerd"] = strings.Join(cds, "-")
	}
	sort.Sort(sort.Reverse(sort.StringSlice(containerdList)))
	sort.Sort(sort.Reverse(sort.StringSlice(cniPluginList)))
	return filePath, containerdList, cniPluginList, nil
}

func DecompressionSystemSourceFile() error {
	if !utils.Exists(yumDataDirectory) || utils.DirectoryIsEmpty(yumDataDirectory) {
		_ = os.Remove(yumDataDirectory)
		if utils.Exists(yumDataFile) {
			log.BKEFormat(log.INFO, "Decompressing the source package...")
			// Ensure integrity of the decompressed file
			targetTemp := yumDataDirectory + ".tmp"
			if utils.Exists(targetTemp) {
				err := os.RemoveAll(targetTemp)
				if err != nil {
					return err
				}
			}
			err := utils.UnTar(yumDataFile, targetTemp)
			if err != nil {
				return err
			}
			err = os.Rename(targetTemp, yumDataDirectory)
			if err != nil {
				return err
			}
		} else {
			err := os.Mkdir(yumDataDirectory, 0644)
			if err != nil {
				return err
			}
		}
		if !utils.Exists(yumDataDirectory) {
			return errors.New(fmt.Sprintf("%s, not found", yumDataDirectory))
		}
	} else {
		log.BKEFormat(log.WARN, "If the source file already exists, skip decompressing the volumes/source.tar.gz file. ")
		log.BKEFormat(log.HINT, fmt.Sprintf("If you want to unzip it again, remove the directory %s", yumDataDirectory))
	}
	return nil
}
